/**
 * 
 */
package it.unitn.testbed.server.serial;

import it.unitn.testbed.server.TestbedServiceImpl;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;

import org.apache.log4j.Logger;

/**
 * A class loader of the the description of serial messages submitted by users.
 * 
 * The classloader first attempts to load a class from the enviornment. There is
 * a list of white-listed classes ({@link #ALLOWED_CLASSES}) and a list of
 * white-listed packages ({@link #ALLOWED_PACKAGES}). If the class cannot be
 * find in this location, an attempt is made to load the class from the files
 * uploaded by users.
 * 
 * @author Stefan Guna
 * 
 */
public class TestbedClassLoader extends ClassLoader {

	/** Classes that are allowed to be used by loaded classes. */
	private static final String[] ALLOWED_CLASSES = { "net.tinyos.message.Message" };
	/** Packages that are allowed to be used by oaded classes. */
	private static final String[] ALLOWED_PACKAGES = { "java.lang.",
			"sun.reflect." };
	public static Logger log = Logger.getLogger(TestbedClassLoader.class
			.getName());
	/** A hashmap holding the files that contain the classes uploaded by users */
	private HashMap<String, File> classFiles;

	/**
	 * Builds a new classloader object.
	 * 
	 * @param serialMsgs
	 *            The class loaded by users.
	 */
	public TestbedClassLoader(HashMap<String, File> serialMsgs) {
		classFiles = serialMsgs;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.ClassLoader#loadClass(java.lang.String)
	 */
	@Override
	public Class<?> loadClass(String name) throws ClassNotFoundException {
		log.trace("Trying to load " + name);
		Class<?> c = null;
		boolean allowed = false;

		for (int i = 0; i < ALLOWED_CLASSES.length; i++)
			if (ALLOWED_CLASSES[i].equals(name)) {
				log.trace("Env class: " + name);
				allowed = true;
				break;
			}

		if (!allowed)
			for (int i = 0; i < ALLOWED_PACKAGES.length; i++)
				if (name.startsWith(ALLOWED_PACKAGES[i])) {
					log.trace("Env package: " + name);
					allowed = true;
					break;
				}

		if (allowed) {
			log.trace(name
					+ " is a white-listed class, loading from the environment");
			try {
				ClassLoader tmpLoader = TestbedServiceImpl.class
						.getClassLoader();
				c = tmpLoader.loadClass(name);
			} catch (Exception e) {
				throw new ClassNotFoundException(
						"Unable to use system classloader " + e.getMessage());
			}
			if (c == null)
				throw new ClassNotFoundException(
						"Unable to load environment class " + name);
			return c;

		}

		try {
			byte b[] = loadClassData(classFiles.get(name));
			c = defineClass(null, b, 0, b.length);
		} catch (Exception e) {
			throw new ClassNotFoundException("Unable to define class "
					+ e.getMessage());
		}
		if (c == null)
			throw new ClassNotFoundException("Unable to define class from "
					+ classFiles.get(name));

		log.trace("Successfully loaded " + c.getCanonicalName());
		return c;
	}

	/**
	 * Converts a file to a byte array to be used by {@link #loadClass(String)}.
	 * 
	 * @param file
	 *            The file to read.
	 * @return A byte array with the contents of {@code file}
	 * @throws IOException
	 *             if an I/O error occurs.
	 */
	private byte[] loadClassData(File file) throws IOException {
		int size = (int) file.length();
		byte result[] = new byte[size];
		FileInputStream fis = new FileInputStream(file);
		DataInputStream dis = new DataInputStream(fis);
		dis.readFully(result);
		dis.close();
		return result;
	}
}
